/*******************************************************************************
 * Copyright (c) 2000, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal.ide.dialogs;

import java.util.TreeSet;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.GlobalBuildAction;
import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
import org.eclipse.ui.internal.util.PrefUtil;

/**	
 * Page used to determine what order projects will be built in 
 * by the workspace.
 */
public class BuildOrderPreferencePage extends PreferencePage implements
        IWorkbenchPreferencePage {

    private IWorkbench workbench;

    private Button defaultOrderButton;

    private Label buildLabel;

    private List buildList;

    private Composite buttonComposite;

    private IntegerFieldEditor maxItersField;

    private String[] defaultBuildOrder;

    private String[] customBuildOrder;

    //Boolean to indicate if we have looked it up
    private boolean notCheckedBuildOrder = true;

    private final String UP_LABEL = IDEWorkbenchMessages.BuildOrderPreference_up;

    private final String DOWN_LABEL = IDEWorkbenchMessages.BuildOrderPreference_down;

    private final String ADD_LABEL = IDEWorkbenchMessages.BuildOrderPreference_add;

    private final String REMOVE_LABEL = IDEWorkbenchMessages.BuildOrderPreference_remove;

    private final String PROJECT_SELECTION_MESSAGE = IDEWorkbenchMessages.BuildOrderPreference_selectOtherProjects;

    private final String DEFAULTS_LABEL = IDEWorkbenchMessages.BuildOrderPreference_useDefaults;

    private final String LIST_LABEL = IDEWorkbenchMessages.BuildOrderPreference_projectBuildOrder;

    private final String NOTE_LABEL = IDEWorkbenchMessages.Preference_note;

    private final String REMOVE_MESSAGE = IDEWorkbenchMessages.BuildOrderPreference_removeNote;

    // whether or not the use defaults option was selected when Apply (or OK) was last pressed
    // (or when the preference page was opened). This represents the most recent applied state.
    private boolean defaultOrderInitiallySelected;

    private IPropertyChangeListener validityChangeListener = new IPropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent event) {
            if (event.getProperty().equals(FieldEditor.IS_VALID)) {
				updateValidState();
			}
        }
    };

    /**
     * Add another project to the list at the end.
     */
    private void addProject() {

        String[] currentItems = this.buildList.getItems();

        IProject[] allProjects = getWorkspace().getRoot().getProjects();

        ILabelProvider labelProvider = new LabelProvider() {
            public String getText(Object element) {
                return (String) element;
            }
        };

        SimpleListContentProvider contentsProvider = new SimpleListContentProvider();
        contentsProvider
                .setElements(sortedDifference(allProjects, currentItems));

        ListSelectionDialog dialog = new ListSelectionDialog(this.getShell(),
                this, contentsProvider, labelProvider,
                PROJECT_SELECTION_MESSAGE) {
        	protected int getShellStyle() {
        		return super.getShellStyle() | SWT.SHEET;
        	}
        };

        if (dialog.open() != Window.OK) {
			return;
		}

        Object[] result = dialog.getResult();

        int currentItemsLength = currentItems.length;
        int resultLength = result.length;
        String[] newItems = new String[currentItemsLength + resultLength];

        System.arraycopy(currentItems, 0, newItems, 0, currentItemsLength);
        System
                .arraycopy(result, 0, newItems, currentItemsLength,
                        result.length);
        this.buildList.setItems(newItems);
    }

    /**
     * Updates the valid state of the page.
     */
    private void updateValidState() {
        setValid(maxItersField.isValid());
    }

    /**
     * Create the list of build paths. If the current build order is empty make the list empty
     * and disable it.
     * @param composite - the parent to create the list in
     * @param enabled - the boolean that indcates if the list will be sensitive initially or not
     */
    private void createBuildOrderList(Composite composite, boolean enabled) {

        Font font = composite.getFont();

        this.buildLabel = new Label(composite, SWT.NONE);
        this.buildLabel.setText(LIST_LABEL);
        this.buildLabel.setEnabled(enabled);
        GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.horizontalSpan = 2;
        this.buildLabel.setLayoutData(gridData);
        this.buildLabel.setFont(font);

        this.buildList = new List(composite, SWT.BORDER | SWT.MULTI
                | SWT.H_SCROLL | SWT.V_SCROLL);
        this.buildList.setEnabled(enabled);
        GridData data = new GridData();
        //Set heightHint with a small value so the list size will be defined by 
        //the space available in the dialog instead of resizing the dialog to
        //fit all the items in the list.
        data.heightHint = buildList.getItemHeight();
        data.verticalAlignment = GridData.FILL;
        data.horizontalAlignment = GridData.FILL;
        data.grabExcessHorizontalSpace = true;
        data.grabExcessVerticalSpace = true;
        this.buildList.setLayoutData(data);
        this.buildList.setFont(font);
    }

    /**
     * Create the widgets that are used to determine the build order.
     *
     * @param parent the parent composite
     * @return the new control
     */
    protected Control createContents(Composite parent) {

    	PlatformUI.getWorkbench().getHelpSystem().setHelp(parent,
                IIDEHelpContextIds.BUILD_ORDER_PREFERENCE_PAGE);

        Font font = parent.getFont();

        //The main composite
        Composite composite = new Composite(parent, SWT.NULL);
        GridLayout layout = new GridLayout();
        layout.numColumns = 2;
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        composite.setLayout(layout);
        GridData data = new GridData();
        data.verticalAlignment = GridData.FILL;
        data.horizontalAlignment = GridData.FILL;
        composite.setLayoutData(data);
        composite.setFont(font);

        String[] buildOrder = getCurrentBuildOrder();
        boolean useDefault = (buildOrder == null);

        createDefaultPathButton(composite, useDefault);
        // List always enabled so user can scroll list.
        // Only the buttons need to be disabled.
        createBuildOrderList(composite, true);
        createListButtons(composite, !useDefault);

        Composite noteComposite = createNoteComposite(font, composite,
                NOTE_LABEL, REMOVE_MESSAGE);
        GridData noteData = new GridData();
        noteData.horizontalSpan = 2;
        noteComposite.setLayoutData(noteData);

        createSpacer(composite);

        createMaxIterationsField(composite);

        createSpacer(composite);

        if (useDefault) {
            this.buildList.setItems(getDefaultProjectOrder());
        } else {
            this.buildList.setItems(buildOrder);
        }

        return composite;

    }

    /**
     * Adds in a spacer.
     * 
     * @param composite the parent composite
     */
    private void createSpacer(Composite composite) {
        Label spacer = new Label(composite, SWT.NONE);
        GridData spacerData = new GridData();
        spacerData.horizontalSpan = 2;
        spacer.setLayoutData(spacerData);
    }

    /**
     * Create the default path button. Set it to selected based on the current workspace
     * build path.
     * @param composite org.eclipse.swt.widgets.Composite
     * @param selected - the boolean that indicates the buttons initial state
     */
    private void createDefaultPathButton(Composite composite, boolean selected) {

        defaultOrderInitiallySelected = selected;

        this.defaultOrderButton = new Button(composite, SWT.LEFT | SWT.CHECK);
        this.defaultOrderButton.setSelection(selected);
        this.defaultOrderButton.setText(DEFAULTS_LABEL);
        SelectionListener listener = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                defaultsButtonSelected(defaultOrderButton.getSelection());
            }
        };
        this.defaultOrderButton.addSelectionListener(listener);

        GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.horizontalSpan = 2;
        this.defaultOrderButton.setLayoutData(gridData);
        this.defaultOrderButton.setFont(composite.getFont());
    }

    /**
     * Create the buttons used to manipulate the list. These Add, Remove and Move Up or Down
     * the list items.
     * @param composite the parent of the buttons
     * @param enableComposite - boolean that indicates if a composite should be enabled
     */
    private void createListButtons(Composite composite, boolean enableComposite) {

        Font font = composite.getFont();

        //Create an intermeditate composite to keep the buttons in the same column
        this.buttonComposite = new Composite(composite, SWT.RIGHT);
        GridLayout layout = new GridLayout();
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        this.buttonComposite.setLayout(layout);
        GridData gridData = new GridData();
        gridData.verticalAlignment = GridData.FILL;
        gridData.horizontalAlignment = GridData.FILL;
        this.buttonComposite.setLayoutData(gridData);
        this.buttonComposite.setFont(font);

        Button upButton = new Button(this.buttonComposite, SWT.CENTER
                | SWT.PUSH);
        upButton.setText(UP_LABEL);
        upButton.setEnabled(enableComposite);
        upButton.setFont(font);
        setButtonLayoutData(upButton);

        SelectionListener listener = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                moveSelectionUp();
            }
        };
        upButton.addSelectionListener(listener);

        Button downButton = new Button(this.buttonComposite, SWT.CENTER
                | SWT.PUSH);
        downButton.setText(DOWN_LABEL);
        downButton.setEnabled(enableComposite);
        listener = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                moveSelectionDown();
            }
        };
        downButton.addSelectionListener(listener);
        downButton.setFont(font);
        setButtonLayoutData(downButton);

        Button addButton = new Button(this.buttonComposite, SWT.CENTER
                | SWT.PUSH);
        addButton.setText(ADD_LABEL);
        listener = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                addProject();
            }
        };
        addButton.addSelectionListener(listener);
        addButton.setEnabled(enableComposite);
        addButton.setFont(font);
        setButtonLayoutData(addButton);

        Button removeButton = new Button(this.buttonComposite, SWT.CENTER
                | SWT.PUSH);
        removeButton.setText(REMOVE_LABEL);
        listener = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                removeSelection();
            }
        };
        removeButton.addSelectionListener(listener);
        removeButton.setEnabled(enableComposite);
        removeButton.setFont(font);
        setButtonLayoutData(removeButton);

    }

    /**
     * Create the field for the maximum number of iterations in the presence
     * of cycles. 
     */
    private void createMaxIterationsField(Composite composite) {
        Composite maxItersComposite = new Composite(composite, SWT.NONE);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        maxItersComposite.setLayoutData(gd);
        maxItersComposite.setFont(composite.getFont());

        maxItersField = new IntegerFieldEditor(
                "", IDEWorkbenchMessages.BuildOrderPreference_maxIterationsLabel, maxItersComposite) { //$NON-NLS-1$
            protected void doLoad() {
                Text text = getTextControl();
                if (text != null) {
                    int value = getWorkspace().getDescription()
                            .getMaxBuildIterations();
                    text.setText(Integer.toString(value));
                }
            }

            protected void doLoadDefault() {
                Text text = getTextControl();
                if (text != null) {
                    int value = ResourcesPlugin.getPlugin()
                            .getPluginPreferences().getDefaultInt(
                                    ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS);
                    text.setText(Integer.toString(value));
                }
                valueChanged();
            }

            protected void doStore() {
                // handled specially in performOK()
                throw new UnsupportedOperationException();
            }
        };
        maxItersField.setValidRange(1, Integer.MAX_VALUE);
        maxItersField.setPage(this);
        maxItersField.setPreferenceStore(getPreferenceStore());
        maxItersField.setPropertyChangeListener(validityChangeListener);
        maxItersField.load();
    }

    /**
     * The defaults button has been selected - update the other widgets as required.
     * @param selected - whether or not the defaults button got selected
     */
    private void defaultsButtonSelected(boolean selected) {
        if (selected) {
            setBuildOrderWidgetsEnablement(false);
            //Cache the current value as the custom order
            customBuildOrder = buildList.getItems();
            buildList.setItems(getDefaultProjectOrder());

        } else {
            setBuildOrderWidgetsEnablement(true);
            String[] buildOrder = getCurrentBuildOrder();
            if (buildOrder == null) {
				buildList.setItems(getDefaultProjectOrder());
			} else {
				buildList.setItems(buildOrder);
			}
        }
    }

    /**
     * Get the project names for the current custom build
     * order stored in the workspace description.
     * 
     * @return java.lang.String[] or null if there is no setting
     */
    private String[] getCurrentBuildOrder() {
        if (notCheckedBuildOrder) {
            customBuildOrder = getWorkspace().getDescription().getBuildOrder();
            notCheckedBuildOrder = false;
        }

        return customBuildOrder;
    }

    /**
     * Get the project names in the default build order
     * based on the current Workspace settings.
     * 
     * @return java.lang.String[]
     */
    private String[] getDefaultProjectOrder() {
        if (defaultBuildOrder == null) {
            IWorkspace workspace = getWorkspace();
            IWorkspace.ProjectOrder projectOrder = getWorkspace()
                    .computeProjectOrder(workspace.getRoot().getProjects());
            IProject[] foundProjects = projectOrder.projects;
            defaultBuildOrder = new String[foundProjects.length];
            int foundSize = foundProjects.length;
            for (int i = 0; i < foundSize; i++) {
                defaultBuildOrder[i] = foundProjects[i].getName();
            }
        }

        return defaultBuildOrder;
    }

    /**
     * Return the Workspace the build order is from.
     * @return org.eclipse.core.resources.IWorkspace
     */
    private IWorkspace getWorkspace() {
        return ResourcesPlugin.getWorkspace();
    }

    /**
     * Return whether or not searchElement is in testArray.
     */
    private boolean includes(String[] testArray, String searchElement) {

        for (int i = 0; i < testArray.length; i++) {
            if (searchElement.equals(testArray[i])) {
				return true;
			}
        }
        return false;

    }

    /**
     * See IWorkbenchPreferencePage. This class does nothing with he Workbench.
     */
    public void init(IWorkbench workbench) {
        this.workbench = workbench;
        setPreferenceStore(PrefUtil.getInternalPreferenceStore());
    }

    /**
     * Move the current selection in the build list down.
     */
    private void moveSelectionDown() {

        //Only do this operation on a single selection
        if (this.buildList.getSelectionCount() == 1) {
            int currentIndex = this.buildList.getSelectionIndex();
            if (currentIndex < this.buildList.getItemCount() - 1) {
                String elementToMove = this.buildList.getItem(currentIndex);
                this.buildList.remove(currentIndex);
                this.buildList.add(elementToMove, currentIndex + 1);
                this.buildList.select(currentIndex + 1);
            }
        }
    }

    /**
     * Move the current selection in the build list up.
     */
    private void moveSelectionUp() {

        int currentIndex = this.buildList.getSelectionIndex();

        //Only do this operation on a single selection
        if (currentIndex > 0 && this.buildList.getSelectionCount() == 1) {
            String elementToMove = this.buildList.getItem(currentIndex);
            this.buildList.remove(currentIndex);
            this.buildList.add(elementToMove, currentIndex - 1);
            this.buildList.select(currentIndex - 1);
        }
    }

    /**
     * Performs special processing when this page's Defaults button has been pressed.
     * In this case change the defaultOrderButton to have it's selection set to true.
     */
    protected void performDefaults() {
        this.defaultOrderButton.setSelection(true);
        defaultsButtonSelected(true);
        maxItersField.loadDefault();
        super.performDefaults();
    }

    /** 
     * OK has been pressed. If the defualt button is pressed then reset the build order to false;
     * otherwise set it to the contents of the list.
     */
    public boolean performOk() {

        String[] buildOrder = null;
        boolean useDefault = defaultOrderButton.getSelection();

        // if use defaults is turned off
        if (!useDefault) {
			buildOrder = buildList.getItems();
		}

        //Get a copy of the description from the workspace, set the build order and then
        //apply it to the workspace.
        IWorkspaceDescription description = getWorkspace().getDescription();
        description.setBuildOrder(buildOrder);
        description.setMaxBuildIterations(maxItersField.getIntValue());
        try {
            getWorkspace().setDescription(description);
        } catch (CoreException exception) {
            //failed - return false
            return false;
        }

        // Perform auto-build if use default is off (because
        // order could have changed) or if use default setting
        // was changed.
        if (!useDefault || (useDefault != defaultOrderInitiallySelected)) {
            defaultOrderInitiallySelected = useDefault;
            // If auto build is turned on, then do a global incremental
            // build on all the projects.
            if (ResourcesPlugin.getWorkspace().isAutoBuilding()) {
                GlobalBuildAction action = new GlobalBuildAction(workbench
                        .getActiveWorkbenchWindow(),
                        IncrementalProjectBuilder.INCREMENTAL_BUILD);
                action.doBuild();
            }
        }

        // Clear the custom build order cache
        customBuildOrder = null;

        return true;
    }

    /**
     * Remove the current selection in the build list.
     */
    private void removeSelection() {

        this.buildList.remove(this.buildList.getSelectionIndices());
    }

    /**
     * Set the widgets that select build order to be enabled or diabled.
     * @param value boolean
     */
    private void setBuildOrderWidgetsEnablement(boolean value) {

        // Only change enablement of buttons. Leave list alone
        // because you can't scroll it when disabled.
        Control[] children = this.buttonComposite.getChildren();
        for (int i = 0; i < children.length; i++) {
            children[i].setEnabled(value);
        }
    }

    /**
     * Return a sorted array of the names of the projects that are already in the currently 
     * displayed names.
     * @return String[]
     * @param allProjects - all of the projects in the workspace 
     * @param currentlyDisplayed - the names of the projects already being displayed
     */
    private String[] sortedDifference(IProject[] allProjects,
            String[] currentlyDisplayed) {

        TreeSet difference = new TreeSet();

        for (int i = 0; i < allProjects.length; i++) {
            if (!includes(currentlyDisplayed, allProjects[i].getName())) {
				difference.add(allProjects[i].getName());
			}
        }

        String[] returnValue = new String[difference.size()];
        difference.toArray(returnValue);
        return returnValue;
    }
}
